package com.zxb.base.monitor.controller;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zxb.base.monitor.vo.*;
import com.zxb.base.monitor.MonitorConfig;
import com.zxb.base.monitor.MonitorStatusEnum;
import com.zxb.base.monitor.vo.Thread;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.lang.management.*;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Zxb
 */
@Slf4j
@RestController
@AllArgsConstructor
public class MonitorController {
    private MonitorConfig monitorConfig;

    /**
     * 巡检
     */
    @Scheduled(cron = "0 0/10 * * * ?")
    public void patrol() {
        monitor();
    }

    /**
     * 预警
     *
     * @return
     */
    @GetMapping("/un/monitor/warn")
    public MonitorStatusEnum monitor() {
        // 不开启，则返回空
        if (BooleanUtil.isFalse(monitorConfig.getEnabled())) {
            return null;
        }

        MonitorStatusEnum result = MonitorStatusEnum.GREEN;

        // 判断CPU
        Cpu cpu = cpu();
        if (NumberUtil.sub(cpu.getSystemCpuUsage().doubleValue() * 100, monitorConfig.getCpuStage2().doubleValue()) >= 0 ||
                NumberUtil.sub(cpu.getProcessCpuUsage().doubleValue() * 100, monitorConfig.getCpuStage2().doubleValue()) >= 0) {
            result = result.max(MonitorStatusEnum.RED);
            log.error("Monitoring capability detection cpu warning!!!" + StrUtil.LF + cpu);
        } else if (NumberUtil.sub(cpu.getSystemCpuUsage().doubleValue() * 100, monitorConfig.getCpuStage1().doubleValue()) >= 0 ||
                NumberUtil.sub(cpu.getProcessCpuUsage().doubleValue() * 100, monitorConfig.getCpuStage1().doubleValue()) >= 0) {
            result = result.max(MonitorStatusEnum.YELLOW);
            log.warn("Monitoring capability detection cpu warning!!!" + StrUtil.LF + cpu);
        }

        // 判断内存
        Memory memory = memory();
        if (NumberUtil.sub(memory.getPhysicalMemoryUsage().doubleValue() * 100, monitorConfig.getMemoryStage2().doubleValue()) >= 0 ||
                NumberUtil.sub(memory.getMemoryUsage().doubleValue() * 100, monitorConfig.getMemoryStage2().doubleValue()) >= 0) {
            result = result.max(MonitorStatusEnum.RED);
            log.warn("Monitoring capability detection memory warning!!!" + StrUtil.LF + memory);
        } else if (NumberUtil.sub(memory.getPhysicalMemoryUsage().doubleValue() * 100, monitorConfig.getMemoryStage1().doubleValue()) >= 0 ||
                NumberUtil.sub(memory.getMemoryUsage().doubleValue() * 100, monitorConfig.getMemoryStage1().doubleValue()) >= 0) {
            result = result.max(MonitorStatusEnum.YELLOW);
            log.warn("Monitoring capability detection memory warning!!!" + StrUtil.LF + memory);
        }

        // 判断磁盘
        Disk disk = disk();
        double diskTotalUsage = disk.getDevices().stream().map(Disk.Drive::getDriveUsage).mapToDouble(BigDecimal::doubleValue).sum();
        if (NumberUtil.sub(diskTotalUsage * 100, monitorConfig.getDiskStage2().doubleValue()) >= 0) {
            result = result.max(MonitorStatusEnum.RED);
            log.warn("Monitoring capability detection disk warning!!!" + StrUtil.LF + disk);
        } else if (NumberUtil.sub(diskTotalUsage * 100, monitorConfig.getDiskStage1().doubleValue()) >= 0) {
            result = result.max(MonitorStatusEnum.YELLOW);
            log.warn("Monitoring capability detection disk warning!!!" + StrUtil.LF + disk);
        }

        return result;
    }

    /**
     * 打印
     *
     * @return
     */
    @GetMapping("/un/monitor")
    public JSONObject print() {
        JSONObject jo = new JSONObject();
        jo.put("serverInfo", server().toJSON());
        jo.put("jvm", jvm().toJSON());
        jo.put("cpu", cpu().toJSON());
        jo.put("memory", memory().toJSON());
        jo.put("disk", disk().toJSON());
        jo.put("heap&nonHeap", heap().toJSON());
        jo.put("thread", thread().toJSON());
        jo.put("gc", gc().toJSON());
        return jo;
    }

    /**
     * 服务器信息
     *
     * @return
     */
    @SneakyThrows
    private Server server() {
        InetAddress inetAddress = InetAddress.getLocalHost();
        OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
        return Server.builder()
                .hostName(inetAddress.getHostName())
                .osName(operatingSystemMXBean.getName())
                .osVersion(operatingSystemMXBean.getVersion())
                .arch(operatingSystemMXBean.getArch())
                .localIp(inetAddress.getHostAddress())
                .build();
    }

    /**
     * Java虚拟机信息
     *
     * @return
     */
    private Jvm jvm() {
        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        Map<String, String> systemProperties = runtimeMXBean.getSystemProperties();
        return Jvm.builder()
                .vmName(runtimeMXBean.getVmName())
                .jdkVersion(systemProperties.get("java.runtime.version"))
                .javaHome(systemProperties.get("java.home"))
                .pid(systemProperties.get("PID"))
                .startTime(LocalDateTimeUtil.of(runtimeMXBean.getStartTime()))
                .runtime(runtimeMXBean.getUptime())
                .startParameters(runtimeMXBean.getInputArguments())
                .build();
    }

    /**
     * CPU
     *
     * @return
     */
    private Cpu cpu() {
        OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
        JSONObject operatingSystemJson = JSON.parseObject(JSON.toJSONString(operatingSystemMXBean));
        return Cpu.builder()
                .availableProcssors(operatingSystemMXBean.getAvailableProcessors())
                .systemCpuUsage(operatingSystemJson.getBigDecimal("systemCpuLoad"))
                .processCpuUsage(operatingSystemJson.getBigDecimal("processCpuLoad"))
                .build();
    }

    /**
     * 内存
     *
     * @return
     */
    private Memory memory() {
        OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
        JSONObject operatingSystemJson = JSON.parseObject(JSON.toJSONString(operatingSystemMXBean));
        long totalPhysicalMemory = operatingSystemJson.getLongValue("totalPhysicalMemorySize");
        long freePhysicalMemory = operatingSystemJson.getLongValue("freePhysicalMemorySize");
        long usedPhysicalMemory = totalPhysicalMemory - freePhysicalMemory;
        Runtime runtime = Runtime.getRuntime();
        return Memory.builder()
                .totalPhysicalMemory(totalPhysicalMemory)
                .freePhysicalMemory(freePhysicalMemory)
                .usedPhysicalMemory(usedPhysicalMemory)
                .physicalMemoryUsage(NumberUtil.toBigDecimal(NumberUtil.div(usedPhysicalMemory, totalPhysicalMemory)))
                .totalMemory(runtime.totalMemory())
                .freeMemory(runtime.freeMemory())
                .usedMemory(runtime.totalMemory() - runtime.freeMemory()).maxMemory(runtime.maxMemory())
                .maxUseMemory(runtime.maxMemory() - runtime.totalMemory() + runtime.freeMemory())
                .memoryUsage(NumberUtil.toBigDecimal(NumberUtil.div(runtime.totalMemory() - runtime.freeMemory(), runtime.totalMemory())))
                .build();
    }

    /**
     * 磁盘信息
     *
     * @return
     */
    private Disk disk() {
        List<Disk.Drive> drives = Stream.of(File.listRoots()).map(file -> Disk.Drive.builder()
                .name(file.toString())
                .totalSpace(file.getTotalSpace())
                .freeSpace(file.getFreeSpace())
                .usedSpace(file.getTotalSpace() - file.getFreeSpace())
                .driveUsage(NumberUtil.toBigDecimal(NumberUtil.div(file.getTotalSpace() - file.getFreeSpace(), file.getTotalSpace())))
                .build()
        ).collect(Collectors.toList());
        return new Disk(drives);
    }

    /**
     * 堆/非堆
     *
     * @return
     */
    private Heap heap() {
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
        MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
        return Heap.builder()
                .heapInit(heapMemoryUsage.getInit())
                .heapMaxMemory(heapMemoryUsage.getMax())
                .heapUsedMemory(heapMemoryUsage.getUsed())
                .heapFreeMemory(heapMemoryUsage.getMax() - heapMemoryUsage.getUsed())
                .nonHeapInit(nonHeapMemoryUsage.getInit())
                .nonHeapMaxMemory(nonHeapMemoryUsage.getMax())
                .nonHeapUsedMemory(nonHeapMemoryUsage.getUsed())
                .nonHeapFreeMemory(nonHeapMemoryUsage.getMax() - nonHeapMemoryUsage.getUsed())
                .build();
    }

    /**
     * 线程信息
     *
     * @return
     */
    private Thread thread() {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        return Thread.builder().threadCount(threadMXBean.getThreadCount()).peakThreadCount(threadMXBean.getPeakThreadCount()).daemonThreadCount(threadMXBean.getDaemonThreadCount()).nonDaemonThreadCount(threadMXBean.getThreadCount() - threadMXBean.getDaemonThreadCount()).totalStartedThreadCount(threadMXBean.getTotalStartedThreadCount()).build();
    }

    /**
     * 垃圾回收信息
     *
     * @return
     */
    private Gc gc() {
        List<Gc.GcCollector> collectors = ManagementFactory.getGarbageCollectorMXBeans().stream().map(garbageCollectorMXBean -> Gc.GcCollector.builder()
                        .name(garbageCollectorMXBean.getName())
                        .count(garbageCollectorMXBean.getCollectionCount())
                        .time(garbageCollectorMXBean.getCollectionTime())
                        .build()).sorted(Comparator.comparing(Gc.GcCollector::getCount).reversed())
                .collect(Collectors.toList());
        return Gc.builder().collectors(collectors).build();
    }
}
